/*
 * Copyright (c) 2009 JJ <fred@info.freednix.az.pl>
 *
 * Permission to use, copy, and modify this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * DEVELOPMENT VERSION
 * BUGS:
 * Binding to one ip address. Wildcard disabled (not implemented ;))
 * $Id: gopherd.c,v 1.3 2009/09/05 23:22:34 fred Exp $
 */
#ifdef __sun__
#define __Solaris__
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#ifndef __Solaris__
#include <sys/syslimits.h>
#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <assert.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "gopherd.h"
#include "gfw.h"
#ifdef __Solaris__
#define INFTIM -1
#endif
static Gserv *gopherd_srv_init(struct gopherd_conf *);
static int gopherd_srv_loop(Gserv *);
static void gopherd_srv_free(Gserv *);
static pid_t gopherd_initu(const char *);
static signed short gopherd_loop(Gserv *);
static Gcent *gopherd_accept(Gserv *);
static Gconn *gopherd_connset(void (*new)(Gcent *));
static void *find_free_slot(Geventset *);
static Gcent *gopherd_conn_add(Gconn *);
static int gopherd_conn_rm(Gconn *, Gcent *);
static Gevent *gopherd_event_add(Geventset *, int, void *);
static Geventset *gopherd_event_newset(int);
static int gopherd_event_freeall(Geventset *);
static int gopherd_event_poll(Geventset *, int);
static int gopherd_event_rm(Geventset *, Gevent *);
static void gopherd_event_freeset(Geventset *);
static int gopherd_sock_read(Gcent *, char *, size_t);
static int gopherd_sock_write(int, const char *, ...);
static int gopherd_send_srvmsg(int, const char *, ...);
static int gopherd_dispatch(Gserv *, Geventset *);
static int gopherd_close(Gcent *);
static int gopherd_send_loop(Gcent *, const char *);
static int gopherd_log_init(int);
static int gopherd_log_end(void);
static int gopherd_log(int, const char *, ...);
static int gopherd_err(int, const char *, ...);
static int gopherd_vlog(int, const char *, va_list);
static char *goph_req_path(char *, size_t, size_t);
static int gopherd_cansee(Gcent *, char *);
static int gopherd_canconn(struct in_addr *);
static int send_binary(int, int);
static int send_index(int, int);
static int extncmp(char *, char *, size_t);
static int exec_cgi(const char *, int, int);
static int pidfile(const char *);
static void sighandlers(int);
static void usage(void);

static struct gopherd_conf gconf = {
	/* gcf_chroot_path */ NULL,
	/* gcf_chdir_path  */ DEFAULT_CHDIR,
	/* gcf_user        */ DEFAULT_USER,
	/* gcf_host        */ DEFAULT_HOST,
	/* gcf_port        */ "70",
	/* gcf_access      */ NULL
};
static sig_atomic_t got_alrm;
static sig_atomic_t got_term;
static sig_atomic_t got_usr1;
static sig_atomic_t got_chld;
static unsigned short debug_flag;
struct glog logging;
#ifdef __Solaris__
#include <libgen.h>
const char *__progname;
#endif

/*
 * Gopher protocol rfc 1436
 */
int
main(int argc, char **argv)
{
	Gserv	*gs;
	int	ch;
	pid_t	chpid;
#ifdef __Solaris__
	extern char *optarg;
	extern int optind;
	__progname = basename((argv[0] != NULL?argv[0]:"gopherd"));
#endif
	signal(SIGTERM, sighandlers);
	signal(SIGINT, sighandlers);
	signal(SIGUSR1, sighandlers);
	siginterrupt(SIGTERM, 1);
	while ((ch = getopt(argc, argv, "u:c:a:h:C:D")) != -1) {
		switch (ch) {
			case 'u':
				gconf.gcf_user = strdup(optarg);
				break;
			case 'a':
				gconf.gcf_access = strdup(optarg);
				break;
			case 'h':
				gconf.gcf_host = strdup(optarg);
				break;
			case 'c':
				gconf.gcf_chdir_path = strdup(optarg);
				break;
			case 'C':
				gconf.gcf_chroot_path = strdup(optarg);
				break;
			case 'D':
				debug_flag++;
				break;
			default:
				usage();
				/* NOTREACHED */
		}
	}
	argv += optind;
	argc -= optind;

	if (gopherd_log_init(GLOG_T_SYSLOG) < 0)
		warn("failed to initialize logging");
	if (!debug_flag) {
#ifdef __Solaris__
		switch (fork()) {
			case 0:
				(void) setsid();
				break;
			default:
				_exit(0);
		}
#else
		(void) daemon(1, 0); /* Screw forking errors... */
#endif
	}
	if (pidfile(DEFAULT_PIDFILE) < 0)
		gopherd_log(GLOG_ERR, "cannot store pid in \"%s\"", DEFAULT_PIDFILE);
	if (gconf.gcf_access != NULL && gfw_init(gconf.gcf_access) == -1)
		gopherd_log(GLOG_ERR, "cannot parse access file \"%s\"", 
							gconf.gcf_access);
	if ((gs = gopherd_srv_init(&gconf)) == NULL)
		gopherd_err(1, "cannot init gopherd");
	if (!(chpid = gopherd_initu(gconf.gcf_user))) {
		if (gopherd_loop(gs) < 0)
			gopherd_log(GLOG_ERR, "Daemon error");
		return (0);
	} else if (chpid == -1) {
		(void) close(gs->gs_sockfd);
		gopherd_srv_free(gs);
		gopherd_log(GLOG_ERR, "failed to initialize a child process");
	} else {
		(void) close(gs->gs_sockfd);
		gs->gs_state = GS_PARENTW;
		bzero(&gs->gs_addr, sizeof(sizeof(struct sockaddr_in)));
		gopherd_srv_loop(gs);
		gopherd_srv_free(gs);
	}
	(void) unlink(DEFAULT_PIDFILE);
	(void) gopherd_log_end();
	return (0);
}

static signed short
gopherd_loop(Gserv *gs)
{
	Gcent		*gc;
	signed short	glstate;
	sigset_t	cursset;
	unsigned int	ltick;

	sigemptyset(&cursset);
	sigaddset(&cursset, SIGUSR1);
	sigaddset(&cursset, SIGTERM);
	sigaddset(&cursset, SIGINT);
	sigprocmask(SIG_SETMASK, &cursset, NULL);
	assert(gs != NULL && "Gserv points to NULL");
#define GLOOP_OK glstate >= 0
#define GLOOP_FAIL() glstate = -1; break
	ltick = 1;
	while (GLOOP_OK) {
		sigdelset(&cursset, SIGUSR1);
		sigdelset(&cursset, SIGTERM);
		sigprocmask(SIG_SETMASK, &cursset, NULL);
		if (got_usr1) {
			got_usr1 = 0;
			gfw_free();
			gopherd_log(GLOG_DEFAULT, "Reloading gfw");
			if (gconf.gcf_access != NULL && gfw_init(gconf.gcf_access) < 0)
				gopherd_log(GLOG_ERR, "Failed to reload gfw");
		}
		if (got_term) 
			break;
		sigaddset(&cursset, SIGTERM);
		sigaddset(&cursset, SIGUSR1);
		sigprocmask(SIG_SETMASK, &cursset, NULL);
		if ((gc = gopherd_accept(gs)) == NULL)
			continue;
		if (gopherd_event_poll(gs->gs_evset, INFTIM) == -1) {
			GLOOP_FAIL();
		}
		gopherd_dispatch(gs, gs->gs_evset);
		if (((ltick++) % 839) == 0) {
			ltick = 1;
			gopherd_event_freeall(gs->gs_evset);
		}
	}
	return (glstate);
}

static Gserv *
gopherd_srv_init(struct gopherd_conf *cf)
{
	Gserv			*gs;
	int			gport;

	assert(cf != NULL && "struct gopherd_conf *cf points to NULL");

	if ((gs = calloc(1, sizeof(Gserv))) == NULL)
		return (NULL);

	inet_pton(AF_INET, (cf->gcf_host != NULL?cf->gcf_host:DEFAULT_HOST),
							&gs->gs_addr.sin_addr);
	gport = (cf->gcf_port != NULL?(int)strtol(cf->gcf_port, NULL, 0):70);
	gs->gs_addr.sin_port = htons(gport?gport:70);
	gs->gs_addr.sin_family = AF_INET;

	if ((gs->gs_sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
		free(gs);
		return (NULL);
	}
	{
		int on = 1;
		/* XXX */
#ifndef __Solaris__
		if (setsockopt(gs->gs_sockfd, SOL_SOCKET, SO_REUSEPORT, 
						&on, sizeof(int)) == -1)
			return (NULL);
#endif
		on = 1;
		if (setsockopt(gs->gs_sockfd, SOL_SOCKET, SO_REUSEADDR, 
						&on, sizeof(int)) == -1)
			return (NULL);
	}
	if (bind(gs->gs_sockfd, (struct sockaddr *) &gs->gs_addr, 
				sizeof(gs->gs_addr)) != 0)
	{
		free(gs);
		return (NULL);
	}
	if (listen(gs->gs_sockfd, 512) == -1) {
		free(gs);
		return (NULL);
	}
	gs->gs_state = GS_LISTEN;
	gs->gs_conn = gopherd_connset(NULL);
	gs->gs_evset = gopherd_event_newset(1000);
	if (cf->gcf_chroot_path != NULL && *(cf->gcf_chroot_path) != '\0') {
		if (chroot(cf->gcf_chroot_path) == -1)
			return (NULL);
		cf->gcf_chdir_path = "/";
	}
	if (cf->gcf_chdir_path == NULL || *(cf->gcf_chdir_path) == '\0') {
		cf->gcf_chdir_path = DEFAULT_CHDIR;
	}
	return (gs);
}

/* XXX */
static int
gopherd_srv_loop(Gserv *gs)
{
	sigset_t	 cursigset;
	int		 status;
	int		 timeout;
	unsigned short	 looping;

	signal(SIGCHLD, sighandlers);
	sigemptyset(&cursigset);
	sigfillset(&cursigset);
	sigdelset(&cursigset, SIGSEGV);
	sigdelset(&cursigset, SIGBUS);
	sigdelset(&cursigset, SIGTERM);
	sigdelset(&cursigset, SIGINT);
	sigprocmask(SIG_SETMASK, &cursigset, NULL);

	timeout = DEFAULT_CHILD_TIMEOUT;
	looping = 1;
	(void) close(gs->gs_sockfd);
	do {
		sigdelset(&cursigset, SIGTERM);
		sigdelset(&cursigset, SIGCHLD);
		sigprocmask(SIG_SETMASK, &cursigset, NULL);
		sleep(1);
		sigaddset(&cursigset, SIGTERM);
		sigaddset(&cursigset, SIGCHLD);
		sigprocmask(SIG_SETMASK, &cursigset, NULL);
		if (got_term)
			(void) kill(gs->gs_child, SIGTERM);
		if (!got_chld) 
			continue;
		if (gs->gs_child != waitpid(gs->gs_child, &status, WNOHANG)) {
			if (--timeout <= 1) {
				(void) kill(gs->gs_child, SIGKILL);
				looping = 0;
			}
		} else if (got_term) 
			looping = 0;
		else {
			gopherd_log(GLOG_DEFAULT, "child process %d died", gs->gs_child);
			got_chld = 0;
		}
	} while (looping);
	return (0);
}

static void
gopherd_srv_free(Gserv *gs)
{
	gopherd_event_freeset(gs->gs_evset);
	/* XXX bug */
	free(gs->gs_conn);
	(void) close(gs->gs_sockfd);
	free(gs);
	return;
}

static Gcent *
gopherd_accept(Gserv *gs)
{
	struct sockaddr_in sin;
	socklen_t	   slen;
	int		   csock;
	Gcent		  *gc;
	int		   on;
	
	assert(gs != NULL && "Gserv points to NULL");
	slen = sizeof(struct sockaddr_in);
	if ((csock = accept(gs->gs_sockfd, (struct sockaddr *) &sin, &slen)) == -1)
		return (NULL);
	/* tcp */
	if (setsockopt(gs->gs_sockfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) == -1)
		gopherd_log(GLOG_ERR, "setsockopt() TCP_NODELAY failure");
	if (!gopherd_canconn(&sin.sin_addr)) {
		close(csock);
		return (NULL);
	}
	if ((gc = gopherd_conn_add(gs->gs_conn)) == NULL)
		return (NULL);
	(void) time(&gc->gc_est_time);
	gc->gc_connfd = csock;
	gc->gc_state = GC_ACCEPT;
	bcopy(&sin, &gc->gc_client, sizeof(struct sockaddr_in));
	gc->gc_event = gopherd_event_add(gs->gs_evset, csock, gc);
	if (gc->gc_event == NULL) {
		gopherd_log(GLOG_ERR, "event not added");
		return (NULL);
	}
	gs->gs_evset->ges_active++;
	return (gc);
}

static Gconn *
gopherd_connset(void (*new)(Gcent *))
{
	Gconn	*gcp;

	if ((gcp = calloc(1, sizeof(Gconn))) == NULL)
		return (NULL);
	gcp->gcp_new = new;
	return (gcp);
}

static Gcent *
gopherd_conn_add(Gconn *gcp)
{
	Gcent	*entry;

	assert(gcp != NULL && "gcp (connection set) points to NULL");
	if ((entry = calloc(1, sizeof(Gcent))) == NULL)
		return (NULL);
	if (gcp->gcp_head == NULL) {
		gcp->gcp_head = entry->gc_prev = entry->gc_next = entry;
	} else {
		entry->gc_next = gcp->gcp_head;
		entry->gc_prev = gcp->gcp_head->gc_prev;
		gcp->gcp_head->gc_prev->gc_next = entry;
		gcp->gcp_head->gc_prev = entry;
	}
	return (entry);
}

static int
gopherd_conn_rm(Gconn *gcp, Gcent *cent)
{
	assert(gcp != NULL && "gcp (connection pool ptr) points to NULL");
	assert(cent != NULL && "cent (connection handle) points to NULL");
	/* one element */
	if (gcp->gcp_head == cent && gcp->gcp_head->gc_next == cent) {
		gcp->gcp_head = NULL;
	} else if (cent == gcp->gcp_head) {
		gcp->gcp_head = cent->gc_next;
		cent->gc_next->gc_prev = cent->gc_prev;
		cent->gc_prev->gc_next = cent->gc_next;
	} else {
		cent->gc_next->gc_prev = cent->gc_prev;
		cent->gc_prev->gc_next = cent->gc_next;
	}
	bzero(cent, sizeof(Gcent));
	free(cent);
	return (1);
}

/* adds a gopher slot */
static Gevent *
gopherd_event_add(Geventset *ges, int fd, void *data)
{
	Gevent *gev;

	assert(ges != NULL && "ges (event pool) points to NULL");
	/* XXX fd error is a non critical error */
	assert(fd > 0 && "Invalid fd number");
	if ((gev = calloc(1, sizeof(Gevent))) == NULL)
		return (NULL);
	if (ges->ges_head == NULL) {
		ges->ges_head = gev->gev_next = gev->gev_prev = gev;
	} else {
		gev->gev_next = ges->ges_head;
		gev->gev_prev = ges->ges_head->gev_prev;
		ges->ges_head->gev_prev->gev_next = gev;
		ges->ges_head->gev_prev = gev;
	}
	(ges->ges_allocated)++;
	gev->gev_fd = fd;
	gev->gev_handle = find_free_slot(ges);
	((struct pollfd *)gev->gev_handle)->fd = fd;
	((struct pollfd *)gev->gev_handle)->events = POLLIN;
	((struct pollfd *)gev->gev_handle)->revents = 0;
	gev->gev_data = data;
	gev->gev_state = ST_USED;
	return (gev);
}

static int
gopherd_event_rm(Geventset *ges, Gevent *rmev)
{	
	assert(ges != NULL && "ges points to NULL");
	assert(rmev != NULL && "rmev points to NULL");
	assert(rmev->gev_state == ST_FREE && "cannot remove nonfree event");
	if (rmev == ges->ges_head && rmev == ges->ges_head->gev_next)
		ges->ges_head = NULL;
	else if (rmev == ges->ges_head) {
		ges->ges_head = rmev->gev_next;
		rmev->gev_prev->gev_next = rmev->gev_next;
		rmev->gev_next->gev_prev = rmev->gev_prev;
	} else {
		rmev->gev_prev->gev_next = rmev->gev_next;
		rmev->gev_next->gev_prev = rmev->gev_prev;
	}
	memset(rmev, 9, sizeof(Gevent));
	free(rmev);
	(ges->ges_allocated)--;
	return (0);
}

/* XXX ges_hpool has to be checked directly */
static void *
find_free_slot(Geventset *ges)
{
	int	n;

	for (n = 0; n < EV_MAX_FREE_SLOTS && n < ges->ges_hp_avail; n++)
		if (ges->ges_freeslots[n] != -1)
			return ((void *)(((struct pollfd *)ges->ges_hpool)+n));
	return (NULL);
}

static int
gopherd_close(Gcent *centry)
{
	int	n;

	n = close(centry->gc_connfd);
	return (n);
}


static int
gopherd_event_poll(Geventset *ges, int timeout)
{
	int	n;

	assert(ges != NULL && "ges (event pool) points to NULL");
	n = poll((struct pollfd *) ges->ges_hpool, (ges->ges_active), timeout);
	return (n);
}

static Geventset *
gopherd_event_newset(int evfds)
{
	Geventset	*evset;

	if ((evset = calloc(1, sizeof(Geventset))) == NULL)
		return (NULL);

	if ((evset->ges_hpool = calloc(evfds, sizeof(struct pollfd))) == NULL)
		return (NULL);

	evset->ges_hp_avail = evfds;
	evset->ges_active = 0;
	{
		int n;
		for (n = 0; n < EV_MAX_FREE_SLOTS && n < evfds; n++)
			evset->ges_freeslots[n] = n;
	}
	while (evfds--) (((struct pollfd *)evset->ges_hpool)+evfds)->fd = -1;
	return (evset);
}

static void
gopherd_event_freeset(Geventset *ev)
{
	free(ev->ges_hpool);
	free(ev);
	return;
}

static int
gopherd_serve(Gcent *centry)
{
	char	*bufp;
	int	bufplen;

	if ((bufp = malloc(2048)) == NULL)
		return (-1);
	if ((bufplen = gopherd_sock_read(centry, bufp, 2048)) <= 0) {
		gopherd_log(GLOG_ERR, "gopherd_sock_read()");
		free(bufp);
		return (-1);
	}
	centry->gc_request = bufp;
	centry->gc_req_siz = bufplen;
	if (goph_req_path(bufp, 2048, bufplen) == NULL) {
		free(bufp);
		return (-1);
	}
	if (!gopherd_cansee(centry, bufp)) {
		free(bufp);
		errno = EACCES;
		return (-1);
	}
	if (gopherd_send_loop(centry, bufp) < 0) {
		if (errno == ENOENT) {
#define ERR_MSG	"3no such file or directory\t\terror.host\t1\r\n.\r\n"
			write(centry->gc_connfd, ERR_MSG, strlen(ERR_MSG));
		}
		free(bufp);
		return (-1);
	}
	free(bufp);
	return (0);
}

static int
gopherd_dispatch(Gserv *gs, Geventset *gevset)
{
	Gevent *gev;
	gev = gevset->ges_head;
	/* foreach event */
	do {
		if (gev->gev_state != ST_USED) {
			gev = gev->gev_next;
			continue;
		}
		if (((struct pollfd *)gev->gev_handle)->revents & POLLIN) {
			if (gopherd_serve((Gcent *)gev->gev_data) == -1) {
				gopherd_log(LOG_ERR, "Failed to serve request");
				gopherd_send_srvmsg(((Gcent *)gev->gev_data)->gc_connfd,
						"Failed to serve request! %s",
						(errno != 0?strerror(errno):""));
			}
			gopherd_close((Gcent *)gev->gev_data);
			((struct pollfd *)gev->gev_handle)->fd = -1;
			((struct pollfd *)gev->gev_handle)->events = 0;
			((struct pollfd *)gev->gev_handle)->revents = 0;
			gopherd_conn_rm(gs->gs_conn, (Gcent *)gev->gev_data);
			gev->gev_state = ST_FREE;
			(gevset->ges_active)--;
		}
		gev = gev->gev_next;
	} while (gev != gevset->ges_head);
	return (0);
}

/* gopherd_sock_read and gopherd_sock_write are not thread safe */
#define G_READ_MAX_WAIT		0xf
#define G_READ_TICK_TIME	0xf4240
static int
gopherd_sock_read(Gcent *centry, char *buffer, size_t bufferlen)
{
	enum {READING, WAITING, ERR, DONE, EOC} rstat = READING;
	ssize_t		rbytes;
	size_t		rbuflen;
	unsigned short	waittim;
	char		*rbufp;
	static char	readbuf[2048];

	errno = 0;
	waittim = 0;
	rbufp = readbuf;
	rbuflen = sizeof(readbuf);
	bzero(rbufp, rbuflen);
	while (rstat != ERR && rstat != DONE && rstat != EOC) {
		rbytes = recv(centry->gc_connfd, rbufp, rbuflen, MSG_DONTWAIT);
		if (rbytes > 0) {
			if (*(rbufp+rbytes-1) == '\n' /* XXX && 
					*(rbufp+rbytes-2) == '\r' XXX */) {
				rstat = DONE;
				rbufp += rbytes;
				rbuflen -= rbytes;
			} else {
				rbufp += rbytes;
				rbuflen -= rbytes;
			}
		} else if (rbytes == 0) {
			rstat = EOC;
		} else if (rbytes < 0 && errno == EAGAIN && 
				waittim <= G_READ_MAX_WAIT) {
			rstat = WAITING;
			waittim++;
		} else if (rbytes < 0 && errno == EAGAIN &&
				waittim > G_READ_MAX_WAIT) {
			rstat = ERR;
			errno = ETIMEDOUT;
		} else if (rbytes < 0 && errno != EAGAIN) {
			rstat = ERR;
		}
		if (rstat == WAITING || rstat == READING)
			usleep(G_READ_TICK_TIME);
	}
	if (rstat == EOC) return 0;
	else if (rstat == DONE) {
		strlcpy(buffer, readbuf, bufferlen);
		return (sizeof(readbuf) - rbuflen);
	}
	return (-1);
}

static int
gopherd_sock_write(int wfd, const char *fmt, ...)
{
	int	wlen;
	int	wrotten;
	va_list	lst;
	static char	writebuf[2048];


	va_start(lst, fmt);
	wlen = vsnprintf(writebuf, sizeof(writebuf), fmt, lst);
	va_end(lst);
	if (wlen <= 0)
		return (-1);
	wrotten = write(wfd, writebuf, wlen);
	if (wrotten != wlen)
		return (-1);
	return (wlen);
}

static int
gopherd_send_loop(Gcent *centry, const char *path)
{
	int	lfilefd;
	char	buffer[200];
	int	ret;

	buffer[0] = '\0';
	ret = 0;
	lfilefd = open(path, O_RDONLY);
	if (lfilefd == -1)
		return (-1);
	if (!extncmp(buffer, ".dot", 4) || !extncmp(buffer, ".dotplus", 8)) {
		if (send_index(lfilefd, centry->gc_connfd) == -1) {
			gopherd_err(GLOG_ERR, "send_index() for \"%s\"", buffer);
			ret = -1;
		}
	} else if (centry->gc_flags & GC_EXEC) {
		centry->gc_flags &= ~GC_EXEC;
		if (exec_cgi(buffer, centry->gc_connfd, 10) < 0)
			gopherd_err(GLOG_ERR, "exec failure");
	} else {
		if (send_binary(lfilefd, centry->gc_connfd) == -1) {
			gopherd_err(GLOG_ERR, "send_binary() for \"%s\"", buffer);
			ret = -1;
		}
	}
	return (ret);
}

static int
gopherd_send_srvmsg(int sockfd, const char *fmt, ...)
{
	va_list args;
	int	bytes;
#define MAX_SRVMSG 2000
	char	msg[MAX_SRVMSG];

	bytes = 0;
	va_start(args, fmt);
	vsnprintf(msg, sizeof(msg), fmt, args);
	bytes = gopherd_sock_write(sockfd, "i%s\t\terror.host\t1\r\n.\r\n", msg);
	va_end(args);
	return (bytes);
}

static int
extncmp(char *lcmp, char *extcmp, size_t ecmplen)
{
	size_t	lcmplen;
	char	*lcp;

	assert(lcmp != NULL && "lcmp points to NULL");
	assert(extcmp != NULL && "extcmp points to NULL");
	if (ecmplen <= 0 || *lcmp == '\0' || *extcmp == '\0')
		return (-1);
	lcmplen = strlen(lcmp);
	if (lcmplen < (ecmplen & 0xfffff))
		return (-1);
	lcp = lcmp;
	lcp += (lcmplen - ecmplen);
	return (strncmp(lcp, extcmp, ecmplen));
}

static int
send_index(int fdfrom, int fdto)
{
	FILE	*ffrom;
	size_t	sent = 0;
	char	buffer[1024];
	int	n;

	if ((ffrom = fdopen(fdfrom, "r")) == NULL)
		return (-1);
	while (fgets(buffer, sizeof buffer, ffrom) != NULL) {
		n = strlen(buffer);
		sent += n;
		if (n < (ssize_t) sizeof(buffer) && n >= 2 &&
			buffer[n-1] == '\n' && buffer[n-2] != '\r')
		{
			buffer[n-1] = '\0';
		}
		(void) gopherd_sock_write(fdto, "%s\r\n", buffer);
	}
	(void) fclose(ffrom);
	return (sent);
}

static int
send_binary(int bfdfrom, int fdto)
{
	char	buffer[2048];
	int	sent = 0;
	int	n;

	while ((n = read(bfdfrom, buffer, sizeof(buffer))) > 0) {
		if (write(fdto, buffer, n) == -1)
			return (-1);
		sent += n;
	}
	(void) close(bfdfrom);
	return (sent);
}

static int
exec_cgi(const char *script_path, int fdto, int timeout)
{
	int	fildes[2];
	int	status;
	int	tim;
	int	waitflg;
	pid_t	chpid;

	signal(SIGALRM, sighandlers);
	siginterrupt(SIGALRM, 1);
	if (pipe(fildes) < 0) {
		gopherd_err(GLOG_ERR, "pipe()");
		return (-1);
	}
	if ((chpid = fork()) == 0) {
		close(fdto);
		if (dup2(fildes[1], STDOUT_FILENO) < 0)
			gopherd_err(1, "Cannot dup2(STDOUT -> pipefd[1])");
		close(fildes[1]);
		execl(script_path, script_path, (char *)NULL);
		gopherd_err(1, "Cannot execute \"%s\"", script_path);
	} else if (chpid > 0) {
		close(fildes[1]);
		waitflg = WNOHANG;
		alarm(5);
		send_binary(fildes[0], fdto);
		alarm(0);
		if (got_alrm) {
			got_alrm = 0;
			warnx("\"%s\" execution timeout", script_path);
		}
		for (tim = 0; tim <= timeout; tim++) {
			if (tim == timeout) {
				kill(chpid, SIGKILL);
				waitflg = 0;
			}
			if (waitpid(chpid, &status, waitflg) == chpid)
				break;
			else
				sleep(1);
		}
		(void) close(fildes[0]);
		(void) close(fdto);

	}

	return (chpid);
}

static char *
goph_req_path(char *bufp, size_t bufplen, size_t readlen)
{
	short		gopher_plus;
	struct stat 	sb;
	char		*reqbuf;
	char		resolved[MAXPATHLEN];

	assert(bufp != NULL && "bufp points to NULL");
	assert(bufplen > 0 && "bufplen is too short");
	gopher_plus = 0;
	
	if ((reqbuf = calloc(bufplen, sizeof(char))) == NULL)
		return (NULL);
	if (readlen > 0 && *(bufp+readlen-1) == '\n') *(bufp+readlen-1) = '\0';
	if (readlen > 1 && *(bufp+readlen-2) == '\r') *(bufp+readlen-2) = '\0';
	strlcpy(reqbuf, bufp, bufplen);
	/* gopher+ */
	if (!strncmp(reqbuf, "\t$", 2)) {
		gopher_plus++;
		*reqbuf = '\0';
		*(reqbuf+1) = '\0';
	}
	snprintf(bufp, bufplen, "%s%s%s", 
		gconf.gcf_chdir_path,
		(strlen(reqbuf) ?  "/" : ""),
		(strlen(reqbuf) ? reqbuf : "")
	);
	errno = 0;
	if (stat(bufp, &sb) == -1) {
		gopherd_log(GLOG_ERR, "goph_req_path() stat(2) failure for \"%s\"", bufp);
		free(reqbuf);
		return (NULL);
	}
	if (S_ISDIR(sb.st_mode))
		 strncat(bufp, (gopher_plus ? "/.dotplus" : "/.dot"), 
				 	bufplen - strlen(bufp));
	if (realpath(bufp, resolved) != NULL) {
		strlcpy(bufp, resolved, bufplen);
	} else {
		free(reqbuf);
		return (NULL);
	}
	return (bufp);
}

static int
gopherd_log_init(int types)
{
	extern const char *__progname;

	logging.l_type = types;
	if (types & GLOG_T_SYSLOG)
		openlog(__progname, LOG_PID|LOG_NDELAY, LOG_LOCAL7);
	if (types & GLOG_T_FILE)
		if ((logging.l_fhandle = fopen(GLOG_FILE_PATH, "a+")) == NULL)
			return (-1);
return (0);	
}

static int
gopherd_vlog(int ev, const char *fmt, va_list vlogargs) 
{
	int	prio;

	switch (ev) {
		case GLOG_DEBUG:
			prio = LOG_DEBUG;
			break;
		case GLOG_ERR:
			prio = LOG_ERR;
			break;
		case GLOG_CRIT:
			prio = LOG_CRIT;
			break;
		case GLOG_GFW:
			prio = LOG_NOTICE;
			break;
		default:
			prio = LOG_INFO;
	}
	if (logging.l_type & GLOG_T_SYSLOG)
		vsyslog(prio, fmt, vlogargs);
	if (logging.l_type & GLOG_T_FILE)
		vfprintf(logging.l_fhandle, fmt, vlogargs);
	return (0);
}

static int
gopherd_log(int ev, const char *fmt, ...)
{
	va_list	args;

	va_start(args, fmt);
	gopherd_vlog(ev, fmt, args);
	va_end(args);
	return (0);
}

static int
gopherd_err(int eval, const char *fmt, ...)
{
	va_list	args;

	va_start(args, fmt);
	gopherd_vlog(LOG_ERR, fmt, args);
	verr(eval, fmt, args);
	/* NOTREACHED */
	va_end(args);
	return (0);
}

static int
gopherd_log_end()
{
	int	ret;

	ret = 0;
	if (logging.l_type & GLOG_T_SYSLOG)
		closelog();
	if (logging.l_type & GLOG_T_FILE)
		ret = fclose(logging.l_fhandle);
	return (ret);
}

static int
gopherd_canconn(struct in_addr *addr)
{
	int	ret;

	ret = gfw_eval(addr, NULL);
	if (ret & RET_LOGIT)
		gopherd_log(GLOG_GFW, "Gopher connection from %s %s",
				inet_ntoa(*addr), 
				((ret & RET_PERMIT) ? "allowed" : "denied"));
	return ((ret & RET_DENY)?0:1);
}

static int
gopherd_cansee(Gcent *ent, char *buf)
{
	int	ret;

	ret = gfw_eval(&(ent->gc_client.sin_addr), buf);
	if (ret & RET_LOGIT)
		gopherd_log(GLOG_GFW, "gopher request \"%s\" from %s %s",
				buf, inet_ntoa(ent->gc_client.sin_addr), 
				((ret & RET_PERMIT) ? "allowed" : "denied"));
	if (ret & RET_CANEXEC) 
		ent->gc_flags |= GC_EXEC;
	return ((ret & RET_DENY)?0:1);
}

static int
gopherd_event_freeall(Geventset *gevp)
{
	Gevent *gev;
	Gevent **freeset, **fsp;
	int	el;

	fsp = freeset = calloc(gevp->ges_allocated + 50, sizeof(Gevent));
	gev = gevp->ges_head;
	el = 0;
	do {
		if (gev->gev_state == ST_FREE) {
			fsp[el++] = gev;
		}
		gev = gev->gev_next;
	} while (gev != gevp->ges_head);
	while (el) {
		gopherd_event_rm(gevp, freeset[--el]);
	}
	return (0);
}

static pid_t
gopherd_initu(const char *user)
{
	struct passwd	*pw;
	pid_t		 chpid;

	setpwent();
	pw = getpwnam((user == NULL ? DEFAULT_USER : user));
	endpwent();
	if (pw == NULL)
		return (-1);
	if ((chpid = fork()) == 0) {
		(void) setgid(pw->pw_gid);
		(void) setuid(pw->pw_uid);
	} else { /* chpid == -1 ? */
		return (chpid);
	}
	return (0);
}

static int
pidfile(const char *path)
{
	int	pathfd;
	char	buffer[80];
	int	len;

	pathfd = open(path, O_WRONLY|O_EXCL|O_CREAT, 0644);
	if (pathfd == -1) {
		gopherd_err(GLOG_ERR, "Cannot open \"%s\" %s", 
				path,
				(errno != ENOENT? strerror(errno):
						"gopherd is already running")
				);
		return (-1);
	}
	len = snprintf(buffer, sizeof buffer, "%d\n", getpid());
	if (write(pathfd, buffer, len) == -1) {
		gopherd_err(GLOG_ERR, "Cannot write(2) \"%s\"", path);
		return (-1);
	}
	(void) close(pathfd);
	return (0);
}

static void
sighandlers(int sig)
{
	switch (sig) {
		case SIGUSR1:
			got_usr1 = 1;
			break;
		case SIGALRM:
			got_alrm = 1;
			break;
		case SIGTERM:
		case SIGINT:
			got_term = 1;
			break;
		case SIGCHLD:
			got_chld = 1;
			break;
	}
	return;
}

static void
usage(void)
{
	extern const char *__progname;

	fprintf(stderr, 
		"usage: %s [-D] [-c chdir] [-C chroot] [-u user] "
		"[-h bindaddr] [-a accesslist]\n", __progname);
	exit(64);
}

/* End of Gopherd */
